package com.xiaoniu.timer.impl;

import com.xiaoniu.timer.*;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.HTreeMap;
import org.mapdb.Serializer;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by sweet on 2017/11/10.
 * ---------------------------
 */
public abstract class TimerAbs implements Timer, Db {

  protected Log log;

  protected ApplicationContext context;

  public TimerAbs() {
    init();
    recoverTask();
  }

  public TimerAbs(ApplicationContext context) {
    init();
    log = new FileLogImpl();
    context = context;
    recoverTask();
  }


  protected static final Vertx VERTX = Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(50000));

  // 周期性任务
  protected static final LocalMap<String, JsonObject> PERIODIC_JOB = VERTX.sharedData().getLocalMap("PeriodicJob");
  // 一次性任务
  protected static final LocalMap<String, JsonObject> TIMER_JOB = VERTX.sharedData().getLocalMap("TimeJob");
  // 存放 uuid - 任务Id
  protected static final LocalMap<String, Long> ID_JOB = VERTX.sharedData().getLocalMap("uuid-jobId");

  // Map DB start ============================
  protected static final DB mapDb = DBMaker.fileDB("vertx-timer-map.db").closeOnJvmShutdown().make();
  protected static final HTreeMap<String, String> PERIODICJOB_DB = mapDb.hashMap("PeriodicJob",
          Serializer.STRING, Serializer.STRING).createOrOpen();
  protected static final HTreeMap<String, String> TIMEJOB_DB = mapDb.hashMap("TimeJob",
          Serializer.STRING, Serializer.STRING).createOrOpen();
  protected static final HTreeMap<String, Long> IDJOB_DB = mapDb.hashMap("db-uuid-jobId",
          Serializer.STRING, Serializer.LONG).createOrOpen();
  // Map DB end  ============================

  /**
   * 执行周期性任务
   *
   * @param taskId
   * @param time
   * @param className
   * @param methodName
   * @param args
   * @return
   */
  @Override
  public Long executePeriodicJob(String taskId, String time, String className, String methodName, String args) {
    try {
      long l = 0;
      String logInfo = "周期性任务，预计执行时间：";

      if (time.length() == 5) {
        String t = TimerUtilsHelp.year() + "-" + TimerUtilsHelp.month() + "-" + TimerUtilsHelp.day() + " " + time;
        l = TimerUtilsHelp.dateToMillisecond(t) > System.currentTimeMillis()
                ? TimerUtilsHelp.dateToMillisecond(t)
                : TimerUtilsHelp.dateToMillisecond(TimerUtilsHelp.year() + "-" + TimerUtilsHelp.month() + "-" +
                (TimerUtilsHelp.day() + 1) + " " + time);
      }

      if (time.length() == 8) {
        String t = TimerUtilsHelp.year() + "-" + TimerUtilsHelp.month() + "-" + time;
        l = TimerUtilsHelp.dateToMillisecond(t) > System.currentTimeMillis()
                ? TimerUtilsHelp.dateToMillisecond(t)
                : TimerUtilsHelp.dateToMillisecond(TimerUtilsHelp.year() + "-" + (TimerUtilsHelp.month() + 1) + "-" + handlerMaxDay(time));
      }

      if (time.length() == 11) {
        String t = TimerUtilsHelp.year() + "-" + time;
        l = TimerUtilsHelp.dateToMillisecond(t) > System.currentTimeMillis()
                ? TimerUtilsHelp.dateToMillisecond(t)
                : TimerUtilsHelp.dateToMillisecond((TimerUtilsHelp.year() + 1) + "-" + time);
      }

      if (l == 0) {
        throw new RuntimeException("参数不正确");
      }
      log.debug(logInfo + TimerUtilsHelp.formatDate(l) + " 执行");

      return VERTX.setTimer(l - System.currentTimeMillis(), r -> {
        ref(taskId, className, methodName, args);
          Long id = executePeriodicJob(taskId, time, className, methodName, args);
          ID_JOB.put(taskId, id);
          IDJOB_DB.put(taskId, id);
          mapDb.commit();
          log.debug("taskId: " + taskId + ", 执行成功, 进入下个周期");
      });
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e.getMessage());
    }
  }

  /**
   * 执行一次性任务
   *
   * @param taskId
   * @param l
   * @param className
   * @param methodName
   * @param args
   * @return
   */
  @Override
  public Long execute(String taskId, long l, String className, String methodName, String args) {
    return VERTX.setTimer(l, res -> {
      ref(taskId, className, methodName, args);
      ID_JOB.remove(taskId);
      TIMER_JOB.remove(taskId);
      IDJOB_DB.remove(taskId);
      TIMEJOB_DB.remove(taskId);
      mapDb.commit();
      log.debug("taskId: " + taskId + ", 执行一次性任务成功");
    });
  }

  @Override
  public JsonObject findById(String taskId) {
    return PERIODIC_JOB.get(taskId) == null
            ? (TIMER_JOB.get(taskId) == null ? null : TIMER_JOB.get(taskId))
            : PERIODIC_JOB.get(taskId);
  }

  @Override
  public Map<String, Object> getMap() {
    Map<String, Object> map = new HashMap<>();
    map.put("PERIODIC_JOB", PERIODICJOB_DB);
    map.put("TIMEJOB_DB", TIMEJOB_DB);
    map.put("IDJOB_DB", IDJOB_DB);
    return map;
  }

  /**
   * 删除任务
   * @param taskId 任务ID
   * @return 返回true 表示删除成功， 返回false 表示删除失败
   */
  @Override
  public boolean removeTask(String taskId) {
    Long aLong = ID_JOB.get(taskId);
    if (aLong != null) {
      ID_JOB.remove(taskId);
      if (PERIODIC_JOB.containsKey(taskId)) {
        PERIODIC_JOB.remove(taskId);
        PERIODICJOB_DB.remove(taskId);
      }

      IDJOB_DB.remove(taskId);
      if (TIMER_JOB.containsKey(taskId)) {
        TIMER_JOB.remove(taskId);
        TIMEJOB_DB.remove(taskId);
      }

      if (VERTX.cancelTimer(aLong)) {
        log.debug("移除任务ID : " + taskId);
        return true;
      }
      mapDb.commit();
      return false;
    }
    return false;
  }

  /**
   * 恢复任务
   */
  @Override
  public void recoverTask() {
    PERIODIC_JOB.forEach((uuid, jsonObject) -> {
      String className = jsonObject.getString("className");
      String methodName = jsonObject.getString("methodName");
      String args = jsonObject.getString("args");
      String time = jsonObject.getString("time");

      Long aLong = executePeriodicJob(uuid, time, className, methodName, args);
      ID_JOB.put(uuid, aLong);
      IDJOB_DB.put(uuid, aLong);
      mapDb.commit();
    });

    TIMER_JOB.forEach((uuid, jsonObject) -> {
      String className = jsonObject.getString("className");
      String methodName = jsonObject.getString("methodName");
      String args = jsonObject.getString("args");
      String time = jsonObject.getString("time");

      long l = 0;
      try {
        l = TimerUtilsHelp.dateToMillisecond(time) - System.currentTimeMillis();
      } catch (ParseException e) {
        e.printStackTrace();
      }

      if (l > 0) {
        log.debug("恢复 一次性任务 timer ==> " + l);
        long l1 = execute(uuid, l, className, methodName, args);

        ID_JOB.put(uuid, l1);
        IDJOB_DB.put(uuid, l1);
        mapDb.commit();
      }
    });
  }

  private void init() {
    PERIODICJOB_DB.forEach((k, v) -> PERIODIC_JOB.put(k, new JsonObject(v)));
    TIMEJOB_DB.forEach((k, v) -> TIMER_JOB.put(k, new JsonObject(v)));
    IDJOB_DB.forEach((k, v) -> ID_JOB.put(k, v));
    mapDb.commit();
    log.info("TimerUtils 初始化...");
  }

  private void ref(String taskId, String className, String methodName, String args) {
    try {
      Class c = Class.forName(className);
      Method method = c.getMethod(methodName, String.class);
      Object o;
      if (context != null) {
        o = context.getBean(c);
      } else {
        o = c.newInstance();
      }
      method.invoke(o, args);
    } catch (ReflectiveOperationException e) {
      e.printStackTrace();
      log.error("taskId: " + taskId + ", 执行失败, 错误原因：" + e.getMessage());
      throw new RuntimeException(e.getMessage());
    }
  }

  private String handlerMaxDay(String s) {
    String[] arr = s.split(" ");
    String day = arr[0];
    Integer maxDay = TimerUtilsHelp.maxDay();
    return Integer.parseInt(day) <= maxDay ? s : maxDay + " " + arr[1];
  }
}
